#include "AssetsWidget.h"
#include <SEUi/SEUi.h>
#include <filesystem>
#include <SEEdit/Layer/EditLayer.h>
#include <SECore/CoreApplication.h>
#include <SERender/Resourses/Loader/TextureLoader.h>
#include <fstream>
#include <Windows.h>
#include <SEEdit/Widget/DragDataLoader.h>
#include <SECore/Manager.h>
#include <SERender/Resourses/Loader/TextureLoader.h>
#include <SEngine/Render/Entity/Loader/ModelLoader.h>
#include <SERender/Resourses/Loader/ShaderLoader.h>
#include <SERender/Resourses/Material.h>
#include <SEngine/Render/RenderFeature/UBORenderFeature.h>
#include <SEAssets/AssetsLoader.h>
#include <SEEdit/Descriptors/AssetsDescriptor.h>
#include <SEWindow/Dialogs/ImportFileDialog.h>
#include <SEWindow/Dialogs/SaveFileDialog.h>
#include <SEngine/Render/Entity/MeshFilter.h>
#include <SEngine/Script/ScriptState.h>
#include <SESerializeYAML/MaterialSerializer.h>

#include <SEDebug/Log/Log.h>

namespace SEngine
{
const std::string scriptTemplate = R"(
function OnLoad()
end

function BeforeUpdate()
end

function OnUpdate(delta)
end

function AfterUpdate() 
end
)";

const std::string shaderTemplate = R"(
// VS_BEGIN
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aCoord;
layout (location = 2) in vec3 aNormal;
layout (location = 3) in vec3 aTangent;
layout (location = 4) in vec3 aBitangent;

uniform mat4 u_model;

layout (std140) uniform EngineUBO
{
    mat4 u_view;
    mat4 u_project;
    vec3 u_camPos;
    vec3 u_camInfo;
    mat4 u_inversePV;
};

void main() {
    gl_Position = u_project * u_view * u_model * vec4(aPos, 1.0f);
}
// VS_END
// FS_BEGIN
#version 330 core 

// Light Packet:
// position.x position.y position.z type(PointLight: 0; DirectLight: 1; AmbientSphereLight: 2)
// color.r    color.g    color.b    strength
// direct.x   direct.y   direct.z   unuse
// unuse      unuse      unuse      unuse
layout (std140) uniform LightUBO
{
    mat4 u_lights[64];
    int u_lightNum;
};

// u_camInfo Packet:
// type(Perspective: 0, Ortho: 1) zNear zFar
layout (std140) uniform EngineUBO
{
    mat4 u_view;
    mat4 u_project;
    vec3 u_camPos;
    vec3 u_camInfo;
    mat4 u_inversePV;
};

uniform samplerCube u_irradianceMap;
uniform samplerCube u_prefilterMap;
uniform sampler2D   u_brdfLUT;

out vec4 FragColor;

void main() {
    FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);
}
// FS_END
)";


    AssetsWidget::AssetsWidget(EditLayer & layer)
        : Widget()
        , m_layer(layer)
    {
        m_opt = m_layer.GetApp().GetDescriptor<Project>()->get().GetOption();
        m_scanFileDialog.SignalUpdate.AddSlot([this](int) { this->OnScanFile(); return SlotState::Keep; });
        m_scanFileDialog.Resize(400.0f, 100.0f);
        m_scanFileDialog.SetTitle("Loading...");
    }

    void AssetsWidget::BeginUpdate()
    {

    }

    void AssetsWidget::UpdateImpl(int delta)
    {
        auto & assetDes = CoreApplication::App().GetDescriptor<AssetsDescriptor>()->get();
        std::filesystem::path root(m_opt.path);
        ImGui::Columns(2, 0, false);
        if (ImGui::Button("Scan")) { 
            CoreApplication::App().SendTask([this](auto &) { ResetTree(std::nullopt); });
        }
        ImGui::NextColumn();
        if (ImGui::Button("Import")) { ImportAssets(); }
        ImGui::Columns(1);
        ImGui::Separator();
        DisplayFileTree("Assets", *assetDes.assets);
        m_scanFileDialog.Update(delta);

    }

    void AssetsWidget::EndUpdate()
    {

    }

    void AssetsWidget::DisplayFileTree(const std::string & name, AssetsElem & root)
    {
        ImGui::PushID(name.c_str());
        DisplayFileTree_(root);
        ImGui::PopID();
    }

    void AssetsWidget::DisplayFileTree_(AssetsElem & node)
    {
        ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_None;
        if (node.type == AssetsElem::Type::DIR) {
            auto isExpand = ImGui::TreeNodeEx(node.name.c_str(), flags);
            if (ImGui::BeginPopupContextItem()) {
                if (ImGui::MenuItem("Show in explorer")) {
                    OnShowInExplorer(node);
                }
                ImGui::Separator();
                if (ImGui::BeginMenu("Create")) {
                    if (ImGui::BeginMenu("Create Material")) {
                        if (ImGui::BeginMenu("Unlit")) {
                            if (ImGui::InputText("Name", m_tempNameBuffer, sizeof(m_tempNameBuffer), ImGuiInputTextFlags_EnterReturnsTrue)) {
                                OnCreateMaterial(node, m_tempNameBuffer, "Unlit");
                                m_tempNameBuffer[0] = '\0';
                            }
                            ImGui::EndMenu();
                        }
                        if (ImGui::BeginMenu("PBR")) {
                            if (ImGui::InputText("Name", m_tempNameBuffer, sizeof(m_tempNameBuffer), ImGuiInputTextFlags_EnterReturnsTrue)) {
                                OnCreateMaterial(node, m_tempNameBuffer, "Standard");
                                m_tempNameBuffer[0] = '\0';
                            }
                            ImGui::EndMenu();
                        }
                        ImGui::EndMenu();
                    }
                    if (ImGui::BeginMenu("Create Script")) {
                        if (ImGui::InputText("Name", m_tempNameBuffer, sizeof(m_tempNameBuffer), ImGuiInputTextFlags_EnterReturnsTrue)) {
                            OnCreateScript(node, m_tempNameBuffer);
                            m_tempNameBuffer[0] = '\0';
                        }
                        ImGui::EndMenu();
                    }
                    if (ImGui::BeginMenu("Create Shader")) {
                        if (ImGui::InputText("Name", m_tempNameBuffer, sizeof(m_tempNameBuffer), ImGuiInputTextFlags_EnterReturnsTrue)) {
                            OnCreateShader(node, m_tempNameBuffer);
                            m_tempNameBuffer[0] = '\0';
                        }
                        ImGui::EndMenu();
                    }
                    ImGui::EndMenu();
                }
                if (ImGui::MenuItem("Import")) {
                    OnImportHere(node);
                }
                ImGui::EndPopup();
            }
            if (isExpand) {
                for (auto & next : node.subElems) {
                    DisplayFileTree_(*next);
                }
                ImGui::TreePop();
            }
        } else {
            ImGui::BeginGroup();
            if (ImGui::TreeNodeEx(node.name.c_str(), flags | ImGuiTreeNodeFlags_Leaf)) {
                ImGui::TreePop();
            }
            if (ImGui::BeginPopupContextItem()) {
                if (ImGui::MenuItem("Delete")) {
                    OnDeleteAssets(node);
                }
                ImGui::EndPopup();
            }
            if (ImGui::IsItemHovered()) {
                ImGui::BeginTooltip();
                bool isTexture = false;
                ForeachType([&](auto loader) {
                    using T = decltype(loader)::Type;
                    if (node.assetsTypeName == AssetsTypeStr<T>::name) {
                        if (auto tex = Manager::GetInstance().Get<T>(node.rPath); tex) {
                            ImGui::Image((ImTextureID)(tex.value().Value().GetId()), ImVec2(128, 128), ImVec2{0.0f, 1.0f}, ImVec2{1.0f, 0.0f});
                            isTexture = true;
                        }
                    }
                }, TypesLoader<Texture, TextureHDR>());
                if (!isTexture) ImGui::Text(node.path.c_str());
                ImGui::EndTooltip();
            }
            if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
                ImGui::Text(node.path.c_str());
                ImGui::SetDragDropPayload(node.assetsTypeName.c_str(), &node, sizeof(node));
                ImGui::EndDragDropSource();
            }
            ImGui::EndGroup();
        }
    }

    void AssetsWidget::ResetTree(std::optional<std::vector<std::string>> importedFiles)
    {
        auto LoadAssets = [this, &importedFiles](AssetsElem& curElem, AssetsLoader::LoaderTable & table) {
            if (importedFiles) {
                for (auto & filePath : importedFiles.value()) {
                    if (filePath == curElem.path) {
                        CoreApplication::App().SendTask([&curElem, &table, this](auto&) {
                            Log::GetInstance().Debug("{}", curElem.path);
                            table.at(curElem.assetsTypeName)(curElem, curElem.rPath);
                        }, EventLoop<CoreApplication&>::TaskMode::Defer);
                    }
                }
            } else {
                CoreApplication::App().SendTask([&curElem, &table, this](auto&) {
                    this->m_loadProgress += (1.0f / this->m_loadProgressTotal);
                    this->m_loadFileName = curElem.path;
                    Log::GetInstance().Debug("{}", curElem.path);
                    table.at(curElem.assetsTypeName)(curElem, curElem.rPath);
                }, EventLoop<CoreApplication&>::TaskMode::Defer);
            }
        };
        auto & assetDes = CoreApplication::App().GetDescriptor<AssetsDescriptor>()->get();
        assetDes.Reset();
        std::filesystem::path root(m_opt.path);
        AssetsLoader loader(root.parent_path().string());
        m_loadProgressTotal += loader.ScanFileTree(*assetDes.assets, root.parent_path().append("Assets").make_preferred().string(), LoadAssets);
        CoreApplication::App().SendTask([this](auto&) {
            this->m_loadProgress = 0.0f;
            this->m_scanFileDialog.SetVisiable(false);
        }, EventLoop<CoreApplication&>::TaskMode::Defer);
        m_scanFileDialog.Popup();
    }

    template<typename T>
    void AddFileType(T & dialog) {
        dialog.AddFileType("PNG(*.png)", "*.png");
        dialog.AddFileType("JPG(*.jpg)", "*.jpg");
        dialog.AddFileType("SEMAT(*.semat)", "*.semat");
        dialog.AddFileType("GLSL(*.glsl)", "*.glsl");
        dialog.AddFileType("COMP(*.comp)", "*.comp");
        dialog.AddFileType("FBX(*.fbx)", "*.fbx");
        dialog.AddFileType("OBJ(*.obj)", "*.obj");
        dialog.AddFileType("STL(*.stl)", "*.stl");
        dialog.AddFileType("LUA(*.lua)", "*.lua");
        dialog.AddFileType("TTF(*.ttf)", "*.ttf");
        dialog.AddFileType("HDR(*.hdr)", "*.hdr");
    }

    void AssetsWidget::ImportAssets()
    {
        ImportFileDialog dialog("Import From");
        AddFileType(dialog);
        EExplorerFlags p_flags = EExplorerFlags::EXPLORER | EExplorerFlags::ALLOWMULTISELECT;
        dialog.Show(p_flags);
        if (!dialog.HasSucceeded()) {
            Log::GetInstance().Info("{}", dialog.GetErrorInfo());
            return;
        }
        auto files = dialog.GetSelectedFileNames();
        auto path = dialog.GetSelectedFilePath();

        SaveFileDialog dialog2("Import To");
        dialog2.SetInitialDirectory(m_opt.path);
        dialog2.Show();
        if (!dialog2.HasSucceeded()) {
            Log::GetInstance().Info("{}", dialog2.GetErrorInfo());
            return;
        }
        auto targetPath = std::filesystem::path(dialog2.GetSelectedFilePath()).parent_path();
        try {
            for (auto fileName : files) {
                std::filesystem::copy(std::filesystem::path(path).append(fileName), targetPath, std::filesystem::copy_options::skip_existing);
                Log::GetInstance().Info("Import file: {}",  std::filesystem::path(path).append(fileName).string());
            }
            CoreApplication::App().SendTask([this](auto &) { ResetTree(std::nullopt); });
        } catch(...) {
            Log::GetInstance().Error("Import Error!: \"{}\" \"{}\"", targetPath.string(), path);
        }
        
    }

    void AssetsWidget::OnImportHere(AssetsElem &node)
    {
        ImportFileDialog dialog("Import From");
        AddFileType(dialog);
        EExplorerFlags p_flags = EExplorerFlags::EXPLORER | EExplorerFlags::ALLOWMULTISELECT;
        dialog.Show(p_flags);
        if (!dialog.HasSucceeded()) {
            Log::GetInstance().Info("{}", dialog.GetErrorInfo());
            return;
        }
        auto files = dialog.GetSelectedFileNames();
        auto path = dialog.GetSelectedFilePath();

        auto targetPath = std::filesystem::path(node.path);
        try {
            std::vector<std::string> filenames;
            for (auto fileName : files) {
                std::filesystem::copy(std::filesystem::path(path).append(fileName), targetPath, std::filesystem::copy_options::skip_existing);
                filenames.emplace_back(std::filesystem::path(targetPath).append(fileName).string());
                Log::GetInstance().Info("Import file: {}",  std::filesystem::path(path).append(fileName).string());
            }
            CoreApplication::App().SendTask([this, filenames] (auto &) { ResetTree(filenames); });
        } catch(...) {
            Log::GetInstance().Error("Import Error!: \"{}\" \"{}\"", targetPath.string(), path);
        }
    }

    void AssetsWidget::OnCreateMaterial(AssetsElem &node, const std::string &name, const std::string & temp)
    {
        std::filesystem::path path(node.path);
        path.append(name + ".semat");
        auto mtl = Manager::GetInstance().Get<Material>(temp).value();
        std::ofstream file(path.string());
        if (file.is_open()) {
            file << mtl.Value().Serialize();            
            file.close();
        }
        std::vector<std::string> filenames{path.string()};
        m_layer.GetApp().SendTask([this, filenames](CoreApplication &) { this->ResetTree(filenames); });
        Log::GetInstance().Info("Create file: {}", path.string());
    }

    void AssetsWidget::OnCreateScript(AssetsElem &node, const std::string &name)
    {
        std::filesystem::path path(node.path);
        path.append(name + ".lua");
        std::ofstream file(path.string());
        if (file.is_open()) {
            file << scriptTemplate;
            file.close();
        }
        std::vector<std::string> filenames{path.string()};
        m_layer.GetApp().SendTask([this, filenames](CoreApplication &) { this->ResetTree(filenames); });
        Log::GetInstance().Info("Create file: {}", path.string());
    }

    void AssetsWidget::OnCreateShader(AssetsElem & node, const std::string & name)
    {   
        std::filesystem::path path(node.path);
        path.append(name + ".glsl");
        std::ofstream file(path.string());
        if (file.is_open()) {
            file << shaderTemplate;
            file.close();
        }
        std::vector<std::string> filenames{path.string()};
        m_layer.GetApp().SendTask([this, filenames](CoreApplication &) { this->ResetTree(filenames); });
        Log::GetInstance().Info("Create file: {}", path.string());
    }

    void AssetsWidget::OnDeleteAssets(AssetsElem &node)
    {
        std::filesystem::path path(node.path);
        std::filesystem::remove(path);
        ForeachType([&](auto loader) {
            using T = decltype(loader)::Type;
            if (node.assetsTypeName == AssetsTypeStr<T>::name) {
                Manager::GetInstance().Remove<T>(node.path);
            }
        }, AssetsAllTypes());
        m_layer.GetApp().SendTask([this](CoreApplication &) { this->ResetTree(std::vector<std::string>()); });
        Log::GetInstance().Info("Remove file: {}", path.string());
    }

    void AssetsWidget::OnShowInExplorer(AssetsElem &node)
    {
        std::string result;
        result.resize(node.path.size());
        for (size_t i = 0; i < node.path.size(); ++i) {
            result[i] = node.path[i] == '/' ? '\\' : node.path[i];
        }
        ShellExecuteA(NULL, "open", result.c_str(), NULL, NULL, SW_SHOWNORMAL);
    }

    void AssetsWidget::OnScanFile()
    {
        ImGui::ProgressBar(m_loadProgress);
        ImGui::Text("Loading: %s", m_loadFileName.c_str());
    }

} // namespace SEngine
